feat: add agentic GitHub Actions workflows (real-LLM, FSM, living-code) - #13
feat: add agentic GitHub Actions workflows (real-LLM, FSM, living-code)#13spiralgang wants to merge 1 commit into
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Code Review
This pull request introduces a real LLM call composite action, a reusable seccomp sandbox runner workflow, and a consolidated 'living-code' agentic engine in Python. The review feedback highlights several critical issues, including an invalid GitHub Actions context variable, a potential command injection vulnerability in the composite action, a malformed JSON string in the seccomp profile, and several robustness and logic bugs in the Python engine (such as unhandled exceptions in the thread pool, missing namespace exposure for the json module, redundant LLM calls, and potential FileNotFoundError on empty directory paths).
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| PROMPT_FILE: ${{ env.RUNNER_TEMP }}/llm/prompt.txt | ||
| SYSTEM_FILE: ${{ env.RUNNER_TEMP }}/llm/system.txt |
There was a problem hiding this comment.
The context ${{ env.RUNNER_TEMP }} is invalid because default environment variables like RUNNER_TEMP are not populated in the GitHub Actions env context. This will evaluate to an empty string, causing the action to attempt to read/write to /llm/prompt.txt and fail due to permission errors. Use ${{ runner.temp }} instead.
PROMPT_FILE: ${{ runner.temp }}/llm/prompt.txt
SYSTEM_FILE: ${{ runner.temp }}/llm/system.txt| - name: "Write prompt + system to files" | ||
| id: write | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| mkdir -p "$RUNNER_TEMP/llm" | ||
| cat > "$RUNNER_TEMP/llm/prompt.txt" <<'PROMPT_EOF' | ||
| ${{ inputs.prompt }} | ||
| PROMPT_EOF | ||
| cat > "$RUNNER_TEMP/llm/system.txt" <<'SYSTEM_EOF' | ||
| ${{ inputs.system }} | ||
| SYSTEM_EOF | ||
| echo "wrote prompt + system" |
There was a problem hiding this comment.
Using inline expression interpolation (${{ inputs.prompt }}) inside a shell script heredoc can lead to syntax errors or command injection vulnerabilities if the prompt contains special characters or the heredoc delimiter (PROMPT_EOF). It is highly recommended to pass these inputs via environment variables and write them to files using printf.
- name: "Write prompt + system to files"
id: write
shell: bash
env:
PROMPT_CONTENT: ${{ inputs.prompt }}
SYSTEM_CONTENT: ${{ inputs.system }}
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/llm"
printf "%s" "$PROMPT_CONTENT" > "$RUNNER_TEMP/llm/prompt.txt"
printf "%s" "$SYSTEM_CONTENT" > "$RUNNER_TEMP/llm/system.txt"
echo "wrote prompt + system"| {"names": ["read","write","open","openat","close","stat","fstat","lstat","poll","select"," | ||
| epoll_wait","fork","vfork","clone","execve","exit","exit_group","wait4","nanosleep","getpid", |
There was a problem hiding this comment.
The JSON string literal contains a newline between "select"," and epoll_wait",". In JSON, literal newlines inside string values are invalid and will cause parsing to fail. Ensure each string is fully enclosed on its own line, with the comma outside the quotes.
{"names": ["read","write","open","openat","close","stat","fstat","lstat","poll","select",
"epoll_wait","fork","vfork","clone","execve","exit","exit_group","wait4","nanosleep","getpid",| self.model = model or os.getenv("LLM_MODEL", "glm-4-flash") | ||
| self.base_url = (base_url or os.getenv("LLM_BASE_URL", | ||
| "https://open.bigmodel.cn/api/paas/v4")).rstrip("/") | ||
| self.api_key = api_key if api_key is not None else os.getenv("LLM_API_KEY", "") |
There was a problem hiding this comment.
The PR description mentions ZHIPU_API_KEY as a required secret, and the default base URL points to Zhipu AI's endpoint. However, LLMClient only checks LLM_API_KEY. Adding a fallback to ZHIPU_API_KEY will prevent unexpected authentication failures for users who configured that secret.
| self.api_key = api_key if api_key is not None else os.getenv("LLM_API_KEY", "") | |
| self.api_key = api_key if api_key is not None else (os.getenv("LLM_API_KEY") or os.getenv("ZHIPU_API_KEY") or "") |
| # expose the real LLM + json to the child namespace (it calls self.llm.chat) | ||
| mod.__dict__["llm"] = llm |
There was a problem hiding this comment.
The comment states that json is exposed to the child namespace, but only llm is actually added to mod.__dict__. Since import statements are banned by the AST audit, child agents have no way to use the json module unless it is explicitly exposed here.
| # expose the real LLM + json to the child namespace (it calls self.llm.chat) | |
| mod.__dict__["llm"] = llm | |
| # expose the real LLM + json to the child namespace (it calls self.llm.chat) | |
| mod.__dict__["llm"] = llm | |
| mod.__dict__["json"] = json |
| def _run(src): | ||
| inst = compile_child(src, llm) | ||
| return inst.execute(task) |
There was a problem hiding this comment.
Since child agents are generated dynamically by an LLM, they are highly prone to syntax errors, AST audit failures, or runtime exceptions. If any child execution fails, the unhandled exception will crash the entire deploy_children thread pool execution. Wrapping the execution in a try-except block will make the manager run much more robust.
| def _run(src): | |
| inst = compile_child(src, llm) | |
| return inst.execute(task) | |
| def _run(src): | |
| try: | |
| inst = compile_child(src, llm) | |
| return inst.execute(task) | |
| except Exception as e: | |
| LOG.warning("Child execution failed: %s", e) | |
| return f"Error: {e}" |
| spec = llm.chat([ | ||
| {"role": "system", "content": "You emit Python child agents. " | ||
| "When asked, output a fenced ```python block defining " | ||
| "class GeneratedAgent with __init__(self, role, goal, llm) and " | ||
| "execute(self, task: dict) -> str that calls self.llm.chat(...)."}, | ||
| {"role": "user", "content": f"WRITE A PYTHON CHILD AGENT for: {base_task}"}, | ||
| ], max_tokens=800) | ||
| m = re.search(r"```python\n(.*?)```", spec, re.DOTALL) | ||
| if not m: | ||
| LOG.warning("manager did not emit a child agent this cycle"); continue | ||
| src = m.group(1) | ||
| # optional mutation: ask to improve the previous child | ||
| if history: | ||
| imp = llm.chat([ | ||
| {"role": "user", "content": f"Improve this child agent:\n{history[-1]}\n" | ||
| "Return ONLY a new ```python ... ``` block."}], max_tokens=800) | ||
| mm = re.search(r"```python\n(.*?)```", imp, re.DOTALL) | ||
| if mm: | ||
| src = mm.group(1) | ||
| history.append(src) |
There was a problem hiding this comment.
In living_code_cycle, a new child agent spec is generated via llm.chat at the start of every cycle. However, if history is not empty, spec is completely overwritten by the mutated/improved version of the previous agent (imp). This wastes LLM API calls and tokens. Consider only generating the initial agent in the first cycle, and mutating the existing history in subsequent cycles.
| STAGE: {state} | ||
| {STAGE_PROMPTS[state]} | ||
| """ | ||
| os.makedirs(os.path.dirname(path), exist_ok=True) |
There was a problem hiding this comment.
If path does not contain a directory component (e.g., if it is just a filename like superlab-generated.yml), os.path.dirname(path) will return an empty string "". Calling os.makedirs("", exist_ok=True) will raise a FileNotFoundError. Check if the directory name is non-empty before calling os.makedirs.
| os.makedirs(os.path.dirname(path), exist_ok=True) | |
| dirname = os.path.dirname(path) | |
| if dirname: | |
| os.makedirs(dirname, exist_ok=True) |
Agentic GitHub Actions workflows derived from your ~/ markdown layouts.
Required secrets (add in repo Settings → Secrets): LLM_API_KEY, LLM_BASE_URL (optional), LLM_MODEL (optional), ZHIPU_API_KEY.
Triggers:
gh workflow run sdlc-examine.yml -f cycle=0
gh workflow run livingcode-matrix.yml -f children=4 -f cycles=2